package com.lh.aop;

import com.lh.model.Pojo;
import com.lh.util.MyLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * Aop 面向切面编程
 *
 * 规范公式：execution(访问权限 方法返回值 方法声明(参数) 异常类型)
 * 简化公式：execution(方法返回值 方法声明(参数))
 *
 * 前置通知：在目标方法执行前执行
 * 后置通知：在目标方法执行后执行，目标方法会将返回值通过形参形式传入，此时可以通过改变对象的属性改变目标方法的部分属性值
 * 环绕通知：环绕通知中反射调用目标方法的执行拿到目标方法的真实返回值，所以可以随便修改目标方法的返回值
 * 最终通知：不管发生什么，最终都会执行，相当于finally代码块
 *
 * @Author LuoHang
 * @Date 2022-05-12 17:15
 **/
@Aspect
@Component
public class MyAspect {
    /**
     * 定义切入的位置
     * 一般用户定义公共的切入点位置
     */
    @Pointcut(value = "execution(* com.lh.service.impl.MyAopServiceImpl.*(..))")
    public void myAdvice(){}

    /**
     * 前置通知实现
     * 访问修饰符：public
     * 返回值类型：void
     * 方法名：自定义
     * 参数：可有可无，有参就必须得是JoinPoint
     * 注解@Before(value="execution(方法的访问权限修饰符 方法的返回值类型 方法声明(参数) 异常类型)")
     *
     * @param point point
     */
    @Before(value = "myAdvice()")
    public void myBefore(JoinPoint point){
        Signature signature = point.getSignature();
        MyLog.info(MyAspect.class, "=============Before前置通知方法开始执行===========");
        //com.lh.service.impl.MyAopServiceImpl
        MyLog.info(MyAspect.class, point.getTarget().getClass().getName());
        //[]
        MyLog.info(MyAspect.class, Arrays.toString(point.getArgs()));
        //getPojo
        MyLog.info(MyAspect.class, signature.getName());
        //com.lh.service.impl.MyAopServiceImpl
        MyLog.info(MyAspect.class, signature.getDeclaringTypeName());
        //com.lh.service.impl.MyAopServiceImpl
        MyLog.info(MyAspect.class, signature.getDeclaringType().getName());
        MyLog.info(MyAspect.class, "=============Before前置通知方法执行结束===========");
    }

    /**
     * 后置通知实现
     * 访问修饰符：public
     * 返回值类型：void
     * 方法名：自定义
     * 参数：可有可无，但是一般都是带有的，这样也可以接收有返回值的目标方法的返回值也可以接收void无返回值目标方法（返回值=null）
     * 注解@AfterReturning(value="execution(访问修饰符 返回值类型 方法的声明(参数) 异常类型)",returning="后置方法的形参名")
     *
     * @param obj 目标方法的返回值
     */
    @AfterReturning(value = "myAdvice()",returning = "obj")
    public void myAfterReturning(Object obj){
        MyLog.info(MyAspect.class, "=============AfterReturning后置通知方法开始执行===========");
        if (obj instanceof Pojo){
            ((Pojo)obj).setFldA("AfterReturning");
        }
        MyLog.info(MyAspect.class,"目标方法的返回值是：" + obj.toString());
        MyLog.info(MyAspect.class, "=============AfterReturning后置通知方法执行结束===========");
    }

    /**
     * 环绕通知实现
     * 访问权限：public
     * 返回值类型：Object
     * 方法名：自定义
     * 参数：ProceedingJoinPoint
     * 注解@Around(value = "myAdvice()")
     * @param point ProceedingJoinPoint
     */
    @Around(value = "myAdvice()")
    public Object myAround1(ProceedingJoinPoint point) throws Throwable {
        MyLog.info(MyAspect.class, "=============Around环绕通知方法开始执行===========");
        MyLog.info(MyAspect.class,"环绕通知的前切Before功能执行");
        //目标方法执行
        Object object = point.proceed(point.getArgs());
        if (object instanceof Pojo){
            ((Pojo)object).setFldB("Around");
        }
        MyLog.info(MyAspect.class,"目标方法的返回值：" + object.toString());
        MyLog.info(MyAspect.class,"环绕通知的后切After功能执行");
        MyLog.info(MyAspect.class, "=============Around环绕通知方法执行结束===========");
        return object;
    }

    @Around(value = "execution(public String com.lh.controller.MyAopController.*(..))")
    public Object myAround2(ProceedingJoinPoint point) throws Throwable {
        //目标方法执行
        Object object = point.proceed(point.getArgs());
        if (object instanceof String){
            return "error";
        }
        return object;
    }

    /**
     * 最终通知实现
     * 访问权限：public
     * 返回值：void
     * 方法名：自定义
     * 参数：无参
     * 注解：@After(value = "execution(* com.lh.controller.MyAopController.*(..))")
     */
    @After(value = "execution(* com.lh.service.impl.MyAopServiceImpl.*(..))")
    public void myAfter(){
        MyLog.info(MyAspect.class, "=============After最终通知方法开始执行===========");
        MyLog.info(MyAspect.class,"不管发生什么最终都会执行我finally");
        MyLog.info(MyAspect.class, "=============After最终通知方法执行结束===========");
    }
}
